﻿//---*Authenticity Seal
/*			                                _ .--.
					                       ( `    )
									     .-'      `--,
			                  _..----.. (             )`-.
			                .'_|` _|` _|(  .__,           )
						   /_|  _|  _|  _(        (_,  .-'
			              ;|  _|  _|  _|  '-'__,--'`--'
			              | _|  _|  _|  _| |
			          _   ||  _|  _|  _|  _|
			        _( `--.\_|  _|  _|  _|/
			      .-'       )--,|  _|  _|.`
				(__, (_      ) )_|  _| /
			      `-.__.\ _,--'\|__|__/
			                    ;____;
			                     \  /
								  OO
			                     |""|
			                     '=='
			                     
           			 Autor: Federico Angel Donnet
*/
//---*Authenticity Seal
//---*Script para testear un character:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class PlayableCharacterScript : MonoBehaviour 
{
	//----------------//
	//---*Atributos:
	//----------------//
	
	//---------------------------------------------------------//
	//---*SubClase que se encarga de los estados del Character:
	//---------------------------------------------------------//
	[System.Serializable]
	public	class Body
	{
		//----------------//
		//---*Atributos:
		//----------------//
		//---*Enumerador de States:
		public	enum STATES
		{
			IDLE,
			RUN,
			JUMP,
			DIE
		};
		//---*Enumerador de Facing:
		public	enum FACING
		{
			LEFT,
			RIGHT,
		};
		//---*Referencia del Transform de este gameObject:
		[HideInInspector]
		public	Transform	theTransform			=	null;
		//---*Referencia del componente Animator de este character (gameObject):
		[HideInInspector]
		public	Animator	anim					=	null;
		//---*Referencia del Transform parent de todos los gameObject que hacen de sprite de este gameObject:
		[HideInInspector]
		public	Transform	spritesParent			=	null;
		//---*Estado actual del Character:
		public	STATES		currentState			=	STATES.IDLE;
		//---*Flag del Facing:
		[HideInInspector]
		public	FACING		facing					=	FACING.RIGHT;
		//---*Flag que indica si el sprite esta o no invertido en eje X:
		[HideInInspector]
		public	bool		flippedX				=	false;
		//----------------//
		//---*Metodos:
		//----------------//
		//---*Inicia y prepara todo:
		public	void Init()
		{
			//---*Referenciamos al componente "Animator" de este GameObject en la subclase "Body":
			this.anim								=	this.theTransform.gameObject.GetComponent<Animator>();
			
		}
		//---*Invierte el Sprite Horizontalmente:
		public	void FlipX()
		{
			this.flippedX = !this.flippedX;
			//---*Para invertir un Sprite Horizontalmente solo multiplicamos la escala en X por -1:
			Vector3 theScale = this.spritesParent.localScale;
			theScale.x 	*= -1;
			this.spritesParent.localScale = theScale;
			
		}
		//---*Invierte el Sprite Verticalmente:
		public	void FlipY()
		{
			//---*Para invertir un Sprite Verticualmente solo multiplicamos la escala en Y por -1:
			Vector3 theScale = this.spritesParent.localScale;
			theScale.y *= -1;
			this.spritesParent.localScale = theScale;
		}
		public	void changeState(STATES newState)
		{
			this.currentState	=	newState;
		}
		
		
	};
	//---------------------------------------------------------//
	//---*SubClase que se encarga del Movimiento y Colisiones del Character:
	//---------------------------------------------------------//
	[System.Serializable]
	public	class Locomotion
	{
		//---*Enumerador de Collision Flags:
		public	enum COLFLAG
		{
			NONE	=	0,
			FLOOR	=	2,
			CEIL	=	8,
			LEFT	=	16,
			RIGHT	=	32,
			WALL	=	64,
			
		};
		//----------------//
		//---*Atributos:
		//----------------//
		//---*Referencia del Transform de este gameObject:
		[HideInInspector]
		public	Transform	theTransform			=	null;
		//---*Referencia del Body del gameObject:
		[HideInInspector]
		public	Body		body					=	null;
		//---*Fuerza que se le aplica al rb2D de este character para trasladarlo
		//---*Sobre el eje X:
		public float 		moveSpeedX				=	365.0f;			
		//---*Movimiento sobre eje Y:
		public float 		moveSpeedY				=	0.0f;		
		//---*Velocidad maxima de traslacion sobre el eje X:
		public float 		maxSpeedX				=	5.0f;	
		//---*Velocidad que se aplica al saltar:
		public float		jumpSpeed				=	320.0f;
		//---*Velocidad de caida:
		public float		Gravity					=	365.0f;
		//---*Velocidad maxima de movimiento en eje Y:
		public float 		maxSpeedY 				=	5.0f;
		//---*Transform que nos sirve para obtener los childs que tienen
		//---*como funcion el chequeo de colisiones en diversos puntos de interes:
		[HideInInspector]
		public	Transform	ColPointsCheckParent	=	null;
		//---*Lista de los transforms encargados del FloorCheck:
		List<Transform>		floorCheck				=	null;
		//---*Lista de los transforms encargados del WallCheck:
		List<Transform>		wallCheck				=	null;
		//---*Otros atributos Auxiliares:
		//---*Mask del Piso ("Ground"):
		[HideInInspector]
		public	int			groundMask				=	0;
		[HideInInspector]
		public	COLFLAG		isTouch					=	COLFLAG.NONE;
		//----------------//
		//---*Metodos:
		//----------------//
		//---*Init/Start 
		//---*Prepara todo lo necesario para el chequeo de colisiones:
		public	void Init()
		{

			//---*Seteamos los mask de colisiones de interes:
			this.groundMask							=	LayerMask.NameToLayer("Ground");
			//---*Obtenemos la referencia del parent que contiene los colliders points check de este character:
			this.ColPointsCheckParent				=	this.theTransform.Find("ColliderPointsCheck");
			//---*Si existe la referencia del parent de puntos de colisiones:
			if(this.ColPointsCheckParent != null)
			{
				//---*Instanciamos la lista de FloorCheck:
				this.floorCheck 					=	new List<Transform>();
				
				//---*Buscamos el child de ColPointsCheckParent que sea el encargado 
				//---*del floorCheck:
				Transform floorCheckParent			= 	this.ColPointsCheckParent.Find("FloorCheck");
				//---*Si dicho parent existe:
				if(floorCheckParent!=null)
				{
					//---*Almacenamos todos sus childs en la lista de floorCheck:
					foreach (Transform child in floorCheckParent) 
					{
						//---*Almacena el Vector3 (position) del child a la lista:
						this.floorCheck.Add(child);
					}
					
				}
				
				//---*Instanciamos la lista de WallCheck:
				this.wallCheck 						=	new List<Transform>();
				//---*Buscamos el child de ColPointsCheckParent que sea el encargado 
				//---*del wallCheck:
				Transform wallCheckParent			= 	this.ColPointsCheckParent.Find("WallCheck");
				//---*Si dicho parent existe:
				if(wallCheckParent!=null)
				{
					//---*Almacenamos todos sus childs en la lista de wallCheck:
					foreach (Transform child in wallCheckParent) 
					{
						//---*Almacena el Vector3 (position) del child a la lista:
						this.wallCheck.Add(child);
					}
					
				}
			}
			
		}
		//---*Chequeo de rutina:
		//---*Sirve para ver si el Character se encuentra tocando piso y otras cosas:
		public	void Check()
		{
			//---*El flag de colision inicia en none antes de todos los chequeos:
			this.isTouch	=	COLFLAG.NONE;
			//---*Chequea si toca el suelo:
			FloorCheck();
			//---*Chequea si toca una pared:
			WallCheck();
			//---*Chequeo de colisiones:
			CollideCheck();
			
		}
		//---*Chequeo de colisiones a lo pseudo Old School:
		void CollideCheck()
		{
			//---*Ve si en un area determinada que rodea al Character hay colision con gameObjects de tipo "Item":
			Collider2D[] col = Physics2D.OverlapCircleAll(new Vector2(this.theTransform.position.x,this.theTransform.position.y),0.25f,1<<LayerMask.NameToLayer("Item"));
			//---*Si los hay:
			if(col.Length>0)
			{
				//---*Los recorre para indicarles que fueron "juntados":
				for(int n=0;n<col.Length;n++)
				{
					//---*Llama a un Callback que responde a la colision:
					//col[n].gameObject.GetComponent<CoinItemScript>().onCollide();
				}
			}

		}
		void FloorCheck()
		{


			//---*Recorremos la lista de floorCheck para ver si algun member toca el suelo:
			int index = 0;
			while (index<this.floorCheck.Count)
			{
				//---*Ve si alguin punto de la lista de floorCheck choca con algo que sea del layer "Ground":
				if(Physics2D.Linecast(this.theTransform.position, this.floorCheck[index].position, 1 << this.groundMask))
				{
					//---*Si al menos ya 1 point toca el suelo, se aniadie al bit:
					this.isTouch |= COLFLAG.FLOOR;
				}
				index+=1;

			}
			
			
		}
		
		
		void WallCheck()
		{
			//---*Recorremos la lista de wallCheck para ver si algun member toca el ground:
			int index = 0;
			while (index<this.wallCheck.Count)
			{				
				//---*Ve si alguin punto de la lista de wallCheck choca con algo que sea del layer "Ground":
				if(Physics2D.Linecast(this.theTransform.position, this.wallCheck[index].position, 1 << this.groundMask))
				{
					//---*Si al menos ya 1 point toca una pared, se aniadie al bit:
					//this.isTouch |= COLFLAG.WALL;
					//---*Vemos de que lado se trata:
					//---*Swichea en base al nombre de Child:
					switch(this.wallCheck[index].name)
					{
						case "WallCheckPointLeft":
							//---*Toca con la Izquierda del Char:
							this.isTouch |= COLFLAG.LEFT;
						break;
						case "WallCheckPointRight":							
							//---*Toca con la Derecha del Char:
							this.isTouch |= COLFLAG.RIGHT;
						break;
					}
					
				}
				index+=1;
			}
			
		}
		//---*Aplica movimiento en eje Y (Gravedad):
		public	void MoveY()
		{
			if(!this.isTouching(COLFLAG.FLOOR))
			{
				this.moveSpeedY += this.Gravity * Time.deltaTime;
			}
			Vector3 trans = new Vector3 (this.theTransform.position.x,this.theTransform.position.y + (this.moveSpeedY*-1.0f)* Time.deltaTime,0.0f);
			this.theTransform.position = trans;

		}
		
		//---*Mueve Horizontalmente al Character:
		public	void MoveX(float hAxis)
		{
			//---*Mueve en Eje X mientras que se permita:
			if(CanMoveX(hAxis))
			{
				Vector3 trans = new Vector3 (this.theTransform.position.x + (this.moveSpeedX*hAxis)* Time.deltaTime,this.theTransform.position.y,0.0f);
				this.theTransform.position = trans;
			}

		}
		//---*Evalua si el Character puede moverce sobre eje X:
		bool CanMoveX(float hAxis)
		{
			//---*Swicheamos en base al facing del body:
			switch(this.body.facing)
			{
			case Body.FACING.LEFT:
				if(!isTouching(COLFLAG.LEFT))
				{
					return true;
				}
				break;
			case Body.FACING.RIGHT:
				if(!isTouching(COLFLAG.RIGHT))
				{
					return true;
				}
				break;
			}
			return false;
		}
		
		//---*Salta (movimiento en eje Y)
		public	void Jump()
		{
			//Vector3 trans = new Vector3 (this.theTransform.position.x,this.theTransform.position.y + (this.jumpSpeed)* Time.deltaTime,0.0f);
			//this.theTransform.position = trans;
			this.moveSpeedY = -this.jumpSpeed;
		}
		
		//---*Ve si el flag de colision corresponde al ingresado
		public	bool isTouching(COLFLAG flag)
		{
			return (this.isTouch & flag) != 0;
			
		}
		
	};
	
	//---*Instancia de la SubClase Body:
	public	Body			body					=	null;
	//---*Instancia de la SubClase Locomotion:
	public	Locomotion		locomotion				=	null;
	
	//----------------//
	//---*Metodos:
	//----------------//
	
	// Use this for initialization
	void Start () 
	{
		//---*Referenciamos el Transform root de este gameObject en body:
		this.body.theTransform						=	this.gameObject.transform;
		//---*Referenciamos el Transform del parent de todos los gameObjects que hacen de sprite de este character:
		this.body.spritesParent						=	this.transform.Find("Sprites");
		//---*Referenciamos el Transform root de este gameObject en locomotion:
		this.locomotion.theTransform				=	this.gameObject.transform;
		//---*Referenciamos el body de locomotion (asi se puede acceder al body desde locomotion):
		this.locomotion.body						=	this.body;
		//---*Iniciamos todo lo necesario del Locomotion para que este listo y preparado para detectar colisiones:
		this.locomotion.Init();
		//---*Iniciamos todo lo necesario del Body para que este listo y preparado para detectar colisiones:
		this.body.Init();
		
		//---*Por ultimo iniciamos en JUMP:
		ChangeToIDLEState();
		
	}
	
	void OnGUI()
	{
		GUI.Label (new Rect (0.0f,0.0f,200.0f,100.0f),"IsTouch: " + this.locomotion.isTouch.ToString());
		GUI.Label (new Rect (0.0f,15.0f,200.0f,100.0f),"Facing: " + this.body.facing.ToString());
		GUI.Label (new Rect (0.0f,30.0f,200.0f,100.0f),"Current State: " + this.body.currentState.ToString());
	}
	void Update () 
	{
		
		//---*Chequeo de rutina del Locomotion:
		this.locomotion.Check();
	}
	
	void FixedUpdate ()
	{
		//---*Entra en la Maquina de Estados Finito:
		FSM();	
		this.locomotion.MoveY();
		
	}
	//----------------//
	//---*Maquina de Estados Finito:
	//----------------//
	void FSM()
	{
		
		//---*Almacenamos el input Horizontal:
		float hAxis = Input.GetAxis("Horizontal");
		if(hAxis<0.0f && !this.body.flippedX)
		{
			this.body.facing = Body.FACING.LEFT;
			this.body.FlipX();
		}
		else if(hAxis>0.0f && this.body.flippedX)
		{
			this.body.facing = Body.FACING.RIGHT;
			this.body.FlipX();
		}
		
		//---*Swicheamos el estado actual de body:
		switch(this.body.currentState)
		{
			//---*Mientras que se encuentre en estado IDLE:
		case Body.STATES.IDLE:

			//---*Antes que nada vemos si hay piso:
			if(!this.locomotion.isTouching(Locomotion.COLFLAG.FLOOR))
			{
				ChangeToJUMPState();
				//this.locomotion.moveSpeedY = this.locomotion.fallSpeed;
				break;
			}
			if(hAxis!=0.0f)
			{
				ChangeToRUNState();
			}
			//---*Salto:
			if(Input.GetButtonDown("Jump") || Input.GetKeyDown(KeyCode.W) || Input.GetKeyDown(KeyCode.UpArrow))
			{
				//---*Si se encuentra sobre algo:
				if(this.locomotion.isTouching(Locomotion.COLFLAG.FLOOR))
				{
					//---*Salta:
					this.locomotion.Jump();	
					//---*Cambia al estado JUMP:
					ChangeToJUMPState();
				}
			}			
			break;
			//---*Mientras que se encuentre en estado RUN:
		case Body.STATES.RUN:

			//---*Si no se esta moviendo el input del axis:
			if(hAxis==0.0f)
			{
				//---*Cambia el estado a IDLE:
				ChangeToIDLEState();
			}
			else 
			{
				//---*Movimiento Horizontal:
				this.locomotion.MoveX(hAxis);
				//---*Salto:
				if(Input.GetButtonDown("Jump") || Input.GetKeyDown(KeyCode.W) || Input.GetKeyDown(KeyCode.UpArrow))
				{
					//---*Si se encuentra sobre algo:
					if(this.locomotion.isTouching(Locomotion.COLFLAG.FLOOR))
					{
						//---*Salta:
						this.locomotion.Jump();	
						//---*Cambia al estado JUMP:
						ChangeToJUMPState();
					}
				}
			}
			break;
			//---*Mientras que se encuentre en estado JUMP:
		case Body.STATES.JUMP:
			//---*Movimiento Horizontal:
			this.locomotion.MoveX(hAxis);
			//---*Si toca el piso, pasa a IDLE:
			if(this.locomotion.isTouching(Locomotion.COLFLAG.FLOOR))
			{
				//---*Si se esta precionando el imput de movimiento:
				if(hAxis!=0.0f)
				{
					ChangeToRUNState();
				}	
				//---*Si no se preciona nada, pasa a IDLE:
				else 
				{
					ChangeToIDLEState();
				}
			}
			
			break;
			
		}	
		
	}
	
	//---*Cambia el estado a IDLE:
	void ChangeToIDLEState()
	{
		//---*Primero anulamos los parametros del Animator:
		this.body.anim.SetBool("isRun",false);
		this.body.anim.SetBool("isJump",false);
		//---*Activa el estado IDLE:
		this.body.anim.SetBool("isIdle",true);
		//---*Cambia el estado desde body:
		this.body.changeState(Body.STATES.IDLE);
		//---*La velocidad de movimiento en eje Y tiene que ser 0!:
		this.locomotion.moveSpeedY = 0.0f;
		
		
	}
	//---*Cambia el estado a RUN:
	void ChangeToRUNState()
	{
		//---*Primero anulamos los parametros del Animator:
		this.body.anim.SetBool("isIdle",false);
		this.body.anim.SetBool("isJump",false);
		//---*Activa el estado RUN:
		this.body.anim.SetBool("isRun",true);
		//---*Cambia el estado desde body:
		this.body.changeState(Body.STATES.RUN);
		//---*La velocidad de movimiento en eje Y tiene que ser 0!:
		this.locomotion.moveSpeedY = 0.0f;
	}
	//---*Cambia el estado a JUMP:
	void ChangeToJUMPState()
	{
		//---*Primero anulamos los parametros del Animator:
		this.body.anim.SetBool("isIdle",false);
		this.body.anim.SetBool("isRun",false);
		//---*Activa el estado JUMP:
		this.body.anim.SetBool("isJump",true);
		//---*Cambia el estado desde body:
		this.body.changeState(Body.STATES.JUMP);
	
	}
	//---*Cambia el estado a DIE:
	void ChangeToDIEState()
	{
		
	}
}